/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 */

/** \file
 * \ingroup edtransform
 */

#include <stdlib.h>

#include "MEM_guardedalloc.h"

#include "DNA_gpencil_types.h"
#include "DNA_mask_types.h"
#include "DNA_mesh_types.h"

#include "BLI_math.h"
#include "BLI_rect.h"

#include "BKE_editmesh.h"
#include "BKE_context.h"
#include "BKE_mask.h"
#include "BKE_scene.h"

#include "GPU_state.h"

#include "ED_clip.h"
#include "ED_gpencil.h"
#include "ED_image.h"
#include "ED_keyframing.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_space_api.h"
#include "ED_node.h"

#include "WM_api.h"
#include "WM_types.h"

#include "UI_interface_icons.h"
#include "UI_resources.h"
#include "UI_view2d.h"

#include "RNA_access.h"

#include "BLF_api.h"
#include "BLT_translation.h"

#include "transform.h"
#include "transform_constraints.h"
#include "transform_convert.h"
#include "transform_draw_cursors.h"
#include "transform_mode.h"
#include "transform_snap.h"

/* Disabling, since when you type you know what you are doing,
 * and being able to set it to zero is handy. */
// #define USE_NUM_NO_ZERO

static void drawTransformApply(const struct bContext *C, ARegion *ar, void *arg);

static void initSnapSpatial(TransInfo *t, float r_snap[3]);

bool transdata_check_local_islands(TransInfo *t, short around)
{
  return ((around == V3D_AROUND_LOCAL_ORIGINS) && ((ELEM(t->obedit_type, OB_MESH))));
}

/* ************************** SPACE DEPENDENT CODE **************************** */

void setTransformViewMatrices(TransInfo *t)
{
  if (t->spacetype == SPACE_VIEW3D && t->ar && t->ar->regiontype == RGN_TYPE_WINDOW) {
    RegionView3D *rv3d = t->ar->regiondata;

    copy_m4_m4(t->viewmat, rv3d->viewmat);
    copy_m4_m4(t->viewinv, rv3d->viewinv);
    copy_m4_m4(t->persmat, rv3d->persmat);
    copy_m4_m4(t->persinv, rv3d->persinv);
    t->persp = rv3d->persp;
  }
  else {
    unit_m4(t->viewmat);
    unit_m4(t->viewinv);
    unit_m4(t->persmat);
    unit_m4(t->persinv);
    t->persp = RV3D_ORTHO;
  }

  calculateCenter2D(t);
  calculateCenterLocal(t, t->center_global);
}

void setTransformViewAspect(TransInfo *t, float r_aspect[3])
{
  copy_v3_fl(r_aspect, 1.0f);

  if (t->spacetype == SPACE_IMAGE) {
    SpaceImage *sima = t->sa->spacedata.first;

    if (t->options & CTX_MASK) {
      ED_space_image_get_aspect(sima, &r_aspect[0], &r_aspect[1]);
    }
    else if (t->options & CTX_PAINT_CURVE) {
      /* pass */
    }
    else {
      ED_space_image_get_uv_aspect(sima, &r_aspect[0], &r_aspect[1]);
    }
  }
  else if (t->spacetype == SPACE_CLIP) {
    SpaceClip *sclip = t->sa->spacedata.first;

    if (t->options & CTX_MOVIECLIP) {
      ED_space_clip_get_aspect_dimension_aware(sclip, &r_aspect[0], &r_aspect[1]);
    }
    else {
      ED_space_clip_get_aspect(sclip, &r_aspect[0], &r_aspect[1]);
    }
  }
  else if (t->spacetype == SPACE_GRAPH) {
    /* depemds on context of usage */
  }
}

static void convertViewVec2D(View2D *v2d, float r_vec[3], int dx, int dy)
{
  float divx = BLI_rcti_size_x(&v2d->mask);
  float divy = BLI_rcti_size_y(&v2d->mask);

  r_vec[0] = BLI_rctf_size_x(&v2d->cur) * dx / divx;
  r_vec[1] = BLI_rctf_size_y(&v2d->cur) * dy / divy;
  r_vec[2] = 0.0f;
}

static void convertViewVec2D_mask(View2D *v2d, float r_vec[3], int dx, int dy)
{
  float divx = BLI_rcti_size_x(&v2d->mask);
  float divy = BLI_rcti_size_y(&v2d->mask);

  float mulx = BLI_rctf_size_x(&v2d->cur);
  float muly = BLI_rctf_size_y(&v2d->cur);

  /* difference with convertViewVec2D */
  /* clamp w/h, mask only */
  if (mulx / divx < muly / divy) {
    divy = divx;
    muly = mulx;
  }
  else {
    divx = divy;
    mulx = muly;
  }
  /* end difference */

  r_vec[0] = mulx * dx / divx;
  r_vec[1] = muly * dy / divy;
  r_vec[2] = 0.0f;
}

void convertViewVec(TransInfo *t, float r_vec[3], double dx, double dy)
{
  if ((t->spacetype == SPACE_VIEW3D) && (t->ar->regiontype == RGN_TYPE_WINDOW)) {
    if (t->options & CTX_PAINT_CURVE) {
      r_vec[0] = dx;
      r_vec[1] = dy;
    }
    else {
      const float mval_f[2] = {(float)dx, (float)dy};
      ED_view3d_win_to_delta(t->ar, mval_f, r_vec, t->zfac);
    }
  }
  else if (t->spacetype == SPACE_IMAGE) {
    if (t->options & CTX_MASK) {
      convertViewVec2D_mask(t->view, r_vec, dx, dy);
    }
    else if (t->options & CTX_PAINT_CURVE) {
      r_vec[0] = dx;
      r_vec[1] = dy;
    }
    else {
      convertViewVec2D(t->view, r_vec, dx, dy);
    }

    r_vec[0] *= t->aspect[0];
    r_vec[1] *= t->aspect[1];
  }
  else if (ELEM(t->spacetype, SPACE_GRAPH, SPACE_NLA)) {
    convertViewVec2D(t->view, r_vec, dx, dy);
  }
  else if (ELEM(t->spacetype, SPACE_NODE, SPACE_SEQ)) {
    convertViewVec2D(&t->ar->v2d, r_vec, dx, dy);
  }
  else if (t->spacetype == SPACE_CLIP) {
    if (t->options & CTX_MASK) {
      convertViewVec2D_mask(t->view, r_vec, dx, dy);
    }
    else {
      convertViewVec2D(t->view, r_vec, dx, dy);
    }

    r_vec[0] *= t->aspect[0];
    r_vec[1] *= t->aspect[1];
  }
  else {
    printf("%s: called in an invalid context\n", __func__);
    zero_v3(r_vec);
  }
}

void projectIntViewEx(TransInfo *t, const float vec[3], int adr[2], const eV3DProjTest flag)
{
  if (t->spacetype == SPACE_VIEW3D) {
    if (t->ar->regiontype == RGN_TYPE_WINDOW) {
      if (ED_view3d_project_int_global(t->ar, vec, adr, flag) != V3D_PROJ_RET_OK) {
        /* this is what was done in 2.64, perhaps we can be smarter? */
        adr[0] = (int)2140000000.0f;
        adr[1] = (int)2140000000.0f;
      }
    }
  }
  else if (t->spacetype == SPACE_IMAGE) {
    SpaceImage *sima = t->sa->spacedata.first;

    if (t->options & CTX_MASK) {
      float v[2];

      v[0] = vec[0] / t->aspect[0];
      v[1] = vec[1] / t->aspect[1];

      BKE_mask_coord_to_image(sima->image, &sima->iuser, v, v);

      ED_image_point_pos__reverse(sima, t->ar, v, v);

      adr[0] = v[0];
      adr[1] = v[1];
    }
    else if (t->options & CTX_PAINT_CURVE) {
      adr[0] = vec[0];
      adr[1] = vec[1];
    }
    else {
      float v[2];

      v[0] = vec[0] / t->aspect[0];
      v[1] = vec[1] / t->aspect[1];

      UI_view2d_view_to_region(t->view, v[0], v[1], &adr[0], &adr[1]);
    }
  }
  else if (t->spacetype == SPACE_ACTION) {
    int out[2] = {0, 0};
#if 0
    SpaceAction *sact = t->sa->spacedata.first;

    if (sact->flag & SACTION_DRAWTIME) {
      //vec[0] = vec[0]/((t->scene->r.frs_sec / t->scene->r.frs_sec_base));
      /* same as below */
      UI_view2d_view_to_region((View2D *)t->view, vec[0], vec[1], &out[0], &out[1]);
    }
    else
#endif
    {
      UI_view2d_view_to_region((View2D *)t->view, vec[0], vec[1], &out[0], &out[1]);
    }

    adr[0] = out[0];
    adr[1] = out[1];
  }
  else if (ELEM(t->spacetype, SPACE_GRAPH, SPACE_NLA)) {
    int out[2] = {0, 0};

    UI_view2d_view_to_region((View2D *)t->view, vec[0], vec[1], &out[0], &out[1]);
    adr[0] = out[0];
    adr[1] = out[1];
  }
  else if (t->spacetype == SPACE_SEQ) { /* XXX not tested yet, but should work */
    int out[2] = {0, 0};

    UI_view2d_view_to_region((View2D *)t->view, vec[0], vec[1], &out[0], &out[1]);
    adr[0] = out[0];
    adr[1] = out[1];
  }
  else if (t->spacetype == SPACE_CLIP) {
    SpaceClip *sc = t->sa->spacedata.first;

    if (t->options & CTX_MASK) {
      MovieClip *clip = ED_space_clip_get_clip(sc);

      if (clip) {
        float v[2];

        v[0] = vec[0] / t->aspect[0];
        v[1] = vec[1] / t->aspect[1];

        BKE_mask_coord_to_movieclip(sc->clip, &sc->user, v, v);

        ED_clip_point_stable_pos__reverse(sc, t->ar, v, v);

        adr[0] = v[0];
        adr[1] = v[1];
      }
      else {
        adr[0] = 0;
        adr[1] = 0;
      }
    }
    else if (t->options & CTX_MOVIECLIP) {
      float v[2];

      v[0] = vec[0] / t->aspect[0];
      v[1] = vec[1] / t->aspect[1];

      UI_view2d_view_to_region(t->view, v[0], v[1], &adr[0], &adr[1]);
    }
    else {
      BLI_assert(0);
    }
  }
  else if (t->spacetype == SPACE_NODE) {
    UI_view2d_view_to_region((View2D *)t->view, vec[0], vec[1], &adr[0], &adr[1]);
  }
}
void projectIntView(TransInfo *t, const float vec[3], int adr[2])
{
  projectIntViewEx(t, vec, adr, V3D_PROJ_TEST_NOP);
}

void projectFloatViewEx(TransInfo *t, const float vec[3], float adr[2], const eV3DProjTest flag)
{
  switch (t->spacetype) {
    case SPACE_VIEW3D: {
      if (t->options & CTX_PAINT_CURVE) {
        adr[0] = vec[0];
        adr[1] = vec[1];
      }
      else if (t->ar->regiontype == RGN_TYPE_WINDOW) {
        /* allow points behind the view [#33643] */
        if (ED_view3d_project_float_global(t->ar, vec, adr, flag) != V3D_PROJ_RET_OK) {
          /* XXX, 2.64 and prior did this, weak! */
          adr[0] = t->ar->winx / 2.0f;
          adr[1] = t->ar->winy / 2.0f;
        }
        return;
      }
      break;
    }
    default: {
      int a[2] = {0, 0};
      projectIntView(t, vec, a);
      adr[0] = a[0];
      adr[1] = a[1];
      break;
    }
  }
}
void projectFloatView(TransInfo *t, const float vec[3], float adr[2])
{
  projectFloatViewEx(t, vec, adr, V3D_PROJ_TEST_NOP);
}

void applyAspectRatio(TransInfo *t, float vec[2])
{
  if ((t->spacetype == SPACE_IMAGE) && (t->mode == TFM_TRANSLATION) &&
      !(t->options & CTX_PAINT_CURVE)) {
    SpaceImage *sima = t->sa->spacedata.first;

    if ((sima->flag & SI_COORDFLOATS) == 0) {
      int width, height;
      ED_space_image_get_size(sima, &width, &height);

      vec[0] *= width;
      vec[1] *= height;
    }

    vec[0] /= t->aspect[0];
    vec[1] /= t->aspect[1];
  }
  else if ((t->spacetype == SPACE_CLIP) && (t->mode == TFM_TRANSLATION)) {
    if (t->options & (CTX_MOVIECLIP | CTX_MASK)) {
      vec[0] /= t->aspect[0];
      vec[1] /= t->aspect[1];
    }
  }
}

void removeAspectRatio(TransInfo *t, float vec[2])
{
  if ((t->spacetype == SPACE_IMAGE) && (t->mode == TFM_TRANSLATION)) {
    SpaceImage *sima = t->sa->spacedata.first;

    if ((sima->flag & SI_COORDFLOATS) == 0) {
      int width, height;
      ED_space_image_get_size(sima, &width, &height);

      vec[0] /= width;
      vec[1] /= height;
    }

    vec[0] *= t->aspect[0];
    vec[1] *= t->aspect[1];
  }
  else if ((t->spacetype == SPACE_CLIP) && (t->mode == TFM_TRANSLATION)) {
    if (t->options & (CTX_MOVIECLIP | CTX_MASK)) {
      vec[0] *= t->aspect[0];
      vec[1] *= t->aspect[1];
    }
  }
}

static void viewRedrawForce(const bContext *C, TransInfo *t)
{
  if (t->options & CTX_GPENCIL_STROKES) {
    bGPdata *gpd = ED_gpencil_data_get_active(C);
    if (gpd) {
      DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
    }
    WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
  }
  else if (t->spacetype == SPACE_VIEW3D) {
    if (t->options & CTX_PAINT_CURVE) {
      wmWindow *window = CTX_wm_window(C);
      WM_paint_cursor_tag_redraw(window, t->ar);
    }
    else {
      /* Do we need more refined tags? */
      if (t->flag & T_POSE) {
        WM_event_add_notifier(C, NC_OBJECT | ND_POSE, NULL);
      }
      else {
        WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
      }

      /* For real-time animation record - send notifiers recognized by animation editors */
      // XXX: is this notifier a lame duck?
      if ((t->animtimer) && IS_AUTOKEY_ON(t->scene)) {
        WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, NULL);
      }
    }
  }
  else if (t->spacetype == SPACE_ACTION) {
    // SpaceAction *saction = (SpaceAction *)t->sa->spacedata.first;
    WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
  }
  else if (t->spacetype == SPACE_GRAPH) {
    // SpaceGraph *sipo = (SpaceGraph *)t->sa->spacedata.first;
    WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
  }
  else if (t->spacetype == SPACE_NLA) {
    WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL);
  }
  else if (t->spacetype == SPACE_NODE) {
    // ED_area_tag_redraw(t->sa);
    WM_event_add_notifier(C, NC_SPACE | ND_SPACE_NODE_VIEW, NULL);
  }
  else if (t->spacetype == SPACE_SEQ) {
    WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, NULL);
    /* Keyframes on strips has been moved, so make sure related editos are informed. */
    WM_event_add_notifier(C, NC_ANIMATION, NULL);
  }
  else if (t->spacetype == SPACE_IMAGE) {
    if (t->options & CTX_MASK) {
      Mask *mask = CTX_data_edit_mask(C);

      WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask);
    }
    else if (t->options & CTX_PAINT_CURVE) {
      wmWindow *window = CTX_wm_window(C);
      WM_paint_cursor_tag_redraw(window, t->ar);
    }
    else if (t->flag & T_CURSOR) {
      ED_area_tag_redraw(t->sa);
    }
    else {
      // XXX how to deal with lock?
      SpaceImage *sima = (SpaceImage *)t->sa->spacedata.first;
      if (sima->lock) {
        WM_event_add_notifier(C, NC_GEOM | ND_DATA, OBEDIT_FROM_VIEW_LAYER(t->view_layer)->data);
      }
      else {
        ED_area_tag_redraw(t->sa);
      }
    }
  }
  else if (t->spacetype == SPACE_CLIP) {
    SpaceClip *sc = (SpaceClip *)t->sa->spacedata.first;

    if (ED_space_clip_check_show_trackedit(sc)) {
      MovieClip *clip = ED_space_clip_get_clip(sc);

      /* objects could be parented to tracking data, so send this for viewport refresh */
      WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);

      WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);
    }
    else if (ED_space_clip_check_show_maskedit(sc)) {
      Mask *mask = CTX_data_edit_mask(C);

      WM_event_add_notifier(C, NC_MASK | NA_EDITED, mask);
    }
  }
}

static void viewRedrawPost(bContext *C, TransInfo *t)
{
  ED_area_status_text(t->sa, NULL);

  if (t->spacetype == SPACE_VIEW3D) {
    /* if autokeying is enabled, send notifiers that keyframes were added */
    if (IS_AUTOKEY_ON(t->scene)) {
      WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
    }

    /* redraw UV editor */
    if (ELEM(t->mode, TFM_VERT_SLIDE, TFM_EDGE_SLIDE) &&
        (t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT)) {
      WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL);
    }

    /* XXX temp, first hack to get auto-render in compositor work (ton) */
    WM_event_add_notifier(C, NC_SCENE | ND_TRANSFORM_DONE, CTX_data_scene(C));
  }

#if 0  // TRANSFORM_FIX_ME
  if (t->spacetype == SPACE_VIEW3D) {
    allqueue(REDRAWBUTSOBJECT, 0);
    allqueue(REDRAWVIEW3D, 0);
  }
  else if (t->spacetype == SPACE_IMAGE) {
    allqueue(REDRAWIMAGE, 0);
    allqueue(REDRAWVIEW3D, 0);
  }
  else if (ELEM(t->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_GRAPH)) {
    allqueue(REDRAWVIEW3D, 0);
    allqueue(REDRAWACTION, 0);
    allqueue(REDRAWNLA, 0);
    allqueue(REDRAWIPO, 0);
    allqueue(REDRAWTIME, 0);
    allqueue(REDRAWBUTSOBJECT, 0);
  }

  scrarea_queue_headredraw(curarea);
#endif
}

/* ************************** TRANSFORMATIONS **************************** */

static void view_editmove(unsigned short UNUSED(event))
{
#if 0  // TRANSFORM_FIX_ME
  int refresh = 0;
  /* Regular:   Zoom in */
  /* Shift:     Scroll up */
  /* Ctrl:      Scroll right */
  /* Alt-Shift: Rotate up */
  /* Alt-Ctrl:  Rotate right */

  /* only work in 3D window for now
   * In the end, will have to send to event to a 2D window handler instead
   */
  if (Trans.flag & T_2D_EDIT) {
    return;
  }

  switch (event) {
    case WHEELUPMOUSE:
      if (G.qual & LR_SHIFTKEY) {
        if (G.qual & LR_ALTKEY) {
          G.qual &= ~LR_SHIFTKEY;
          persptoetsen(PAD2);
          G.qual |= LR_SHIFTKEY;
        }
        else {
          persptoetsen(PAD2);
        }
      }
      else if (G.qual & LR_CTRLKEY) {
        if (G.qual & LR_ALTKEY) {
          G.qual &= ~LR_CTRLKEY;
          persptoetsen(PAD4);
          G.qual |= LR_CTRLKEY;
        }
        else {
          persptoetsen(PAD4);
        }
      }
      else if (U.uiflag & USER_WHEELZOOMDIR) {
        persptoetsen(PADMINUS);
      }
      else {
        persptoetsen(PADPLUSKEY);
      }

      refresh = 1;
      break;
    case WHEELDOWNMOUSE:
      if (G.qual & LR_SHIFTKEY) {
        if (G.qual & LR_ALTKEY) {
          G.qual &= ~LR_SHIFTKEY;
          persptoetsen(PAD8);
          G.qual |= LR_SHIFTKEY;
        }
        else {
          persptoetsen(PAD8);
        }
      }
      else if (G.qual & LR_CTRLKEY) {
        if (G.qual & LR_ALTKEY) {
          G.qual &= ~LR_CTRLKEY;
          persptoetsen(PAD6);
          G.qual |= LR_CTRLKEY;
        }
        else {
          persptoetsen(PAD6);
        }
      }
      else if (U.uiflag & USER_WHEELZOOMDIR) {
        persptoetsen(PADPLUSKEY);
      }
      else {
        persptoetsen(PADMINUS);
      }

      refresh = 1;
      break;
  }

  if (refresh) {
    setTransformViewMatrices(&Trans);
  }
#endif
}

/* ************************************************* */

static bool transform_modal_item_poll(const wmOperator *op, int value)
{
  const TransInfo *t = op->customdata;
  switch (value) {
    case TFM_MODAL_CANCEL: {
      if ((t->flag & T_RELEASE_CONFIRM) && ISMOUSE(t->launch_event)) {
        return false;
      }
      break;
    }
    case TFM_MODAL_PROPSIZE:
    case TFM_MODAL_PROPSIZE_UP:
    case TFM_MODAL_PROPSIZE_DOWN: {
      if ((t->flag & T_PROP_EDIT) == 0) {
        return false;
      }
      break;
    }
    case TFM_MODAL_ADD_SNAP:
    case TFM_MODAL_REMOVE_SNAP: {
      if (t->spacetype != SPACE_VIEW3D) {
        return false;
      }
      else if ((t->tsnap.mode & ~(SCE_SNAP_MODE_INCREMENT | SCE_SNAP_MODE_GRID)) == 0) {
        return false;
      }
      else if (!validSnap(t)) {
        return false;
      }
      break;
    }
    case TFM_MODAL_AXIS_X:
    case TFM_MODAL_AXIS_Y:
    case TFM_MODAL_AXIS_Z:
    case TFM_MODAL_PLANE_X:
    case TFM_MODAL_PLANE_Y:
    case TFM_MODAL_PLANE_Z: {
      if (t->flag & T_NO_CONSTRAINT) {
        return false;
      }
      if (!ELEM(value, TFM_MODAL_AXIS_X, TFM_MODAL_AXIS_Y)) {
        if (t->flag & T_2D_EDIT) {
          return false;
        }
      }
      break;
    }
    case TFM_MODAL_CONS_OFF: {
      if ((t->con.mode & CON_APPLY) == 0) {
        return false;
      }
      break;
    }
    case TFM_MODAL_EDGESLIDE_UP:
    case TFM_MODAL_EDGESLIDE_DOWN: {
      if (t->mode != TFM_EDGE_SLIDE) {
        return false;
      }
      break;
    }
    case TFM_MODAL_INSERTOFS_TOGGLE_DIR: {
      if (t->spacetype != SPACE_NODE) {
        return false;
      }
      break;
    }
    case TFM_MODAL_AUTOIK_LEN_INC:
    case TFM_MODAL_AUTOIK_LEN_DEC: {
      if ((t->flag & T_AUTOIK) == 0) {
        return false;
      }
      break;
    }
  }
  return true;
}

/* called in transform_ops.c, on each regeneration of keymaps */
wmKeyMap *transform_modal_keymap(wmKeyConfig *keyconf)
{
  static const EnumPropertyItem modal_items[] = {
      {TFM_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
      {TFM_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
      {TFM_MODAL_AXIS_X, "AXIS_X", 0, "X axis", ""},
      {TFM_MODAL_AXIS_Y, "AXIS_Y", 0, "Y axis", ""},
      {TFM_MODAL_AXIS_Z, "AXIS_Z", 0, "Z axis", ""},
      {TFM_MODAL_PLANE_X, "PLANE_X", 0, "X plane", ""},
      {TFM_MODAL_PLANE_Y, "PLANE_Y", 0, "Y plane", ""},
      {TFM_MODAL_PLANE_Z, "PLANE_Z", 0, "Z plane", ""},
      {TFM_MODAL_CONS_OFF, "CONS_OFF", 0, "Clear Constraints", ""},
      {TFM_MODAL_SNAP_INV_ON, "SNAP_INV_ON", 0, "Snap Invert", ""},
      {TFM_MODAL_SNAP_INV_OFF, "SNAP_INV_OFF", 0, "Snap Invert (Off)", ""},
      {TFM_MODAL_SNAP_TOGGLE, "SNAP_TOGGLE", 0, "Snap Toggle", ""},
      {TFM_MODAL_ADD_SNAP, "ADD_SNAP", 0, "Add Snap Point", ""},
      {TFM_MODAL_REMOVE_SNAP, "REMOVE_SNAP", 0, "Remove Last Snap Point", ""},
      {NUM_MODAL_INCREMENT_UP, "INCREMENT_UP", 0, "Numinput Increment Up", ""},
      {NUM_MODAL_INCREMENT_DOWN, "INCREMENT_DOWN", 0, "Numinput Increment Down", ""},
      {TFM_MODAL_PROPSIZE_UP, "PROPORTIONAL_SIZE_UP", 0, "Increase Proportional Influence", ""},
      {TFM_MODAL_PROPSIZE_DOWN,
       "PROPORTIONAL_SIZE_DOWN",
       0,
       "Decrease Proportional Influence",
       ""},
      {TFM_MODAL_AUTOIK_LEN_INC, "AUTOIK_CHAIN_LEN_UP", 0, "Increase Max AutoIK Chain Length", ""},
      {TFM_MODAL_AUTOIK_LEN_DEC,
       "AUTOIK_CHAIN_LEN_DOWN",
       0,
       "Decrease Max AutoIK Chain Length",
       ""},
      {TFM_MODAL_EDGESLIDE_UP, "EDGESLIDE_EDGE_NEXT", 0, "Select next Edge Slide Edge", ""},
      {TFM_MODAL_EDGESLIDE_DOWN, "EDGESLIDE_PREV_NEXT", 0, "Select previous Edge Slide Edge", ""},
      {TFM_MODAL_PROPSIZE, "PROPORTIONAL_SIZE", 0, "Adjust Proportional Influence", ""},
      {TFM_MODAL_INSERTOFS_TOGGLE_DIR,
       "INSERTOFS_TOGGLE_DIR",
       0,
       "Toggle Direction for Node Auto-offset",
       ""},
      {TFM_MODAL_TRANSLATE, "TRANSLATE", 0, "Move", ""},
      {TFM_MODAL_ROTATE, "ROTATE", 0, "Rotate", ""},
      {TFM_MODAL_RESIZE, "RESIZE", 0, "Resize", ""},
      {0, NULL, 0, NULL, NULL},
  };

  wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "Transform Modal Map");

  keymap = WM_modalkeymap_add(keyconf, "Transform Modal Map", modal_items);
  keymap->poll_modal_item = transform_modal_item_poll;

  return keymap;
}

static void transform_event_xyz_constraint(TransInfo *t, short key_type, char cmode, bool is_plane)
{
  if (!(t->flag & T_NO_CONSTRAINT)) {
    int constraint_axis, constraint_plane;
    const bool edit_2d = (t->flag & T_2D_EDIT) != 0;
    const char *msg1 = "", *msg2 = "", *msg3 = "";
    char axis;

    /* Initialize */
    switch (key_type) {
      case XKEY:
        msg1 = TIP_("along X");
        msg2 = TIP_("along %s X");
        msg3 = TIP_("locking %s X");
        axis = 'X';
        constraint_axis = CON_AXIS0;
        break;
      case YKEY:
        msg1 = TIP_("along Y");
        msg2 = TIP_("along %s Y");
        msg3 = TIP_("locking %s Y");
        axis = 'Y';
        constraint_axis = CON_AXIS1;
        break;
      case ZKEY:
        msg1 = TIP_("along Z");
        msg2 = TIP_("along %s Z");
        msg3 = TIP_("locking %s Z");
        axis = 'Z';
        constraint_axis = CON_AXIS2;
        break;
      default:
        /* Invalid key */
        return;
    }
    constraint_plane = ((CON_AXIS0 | CON_AXIS1 | CON_AXIS2) & (~constraint_axis));

    if (edit_2d && (key_type != ZKEY)) {
      if (cmode == axis) {
        stopConstraint(t);
      }
      else {
        setUserConstraint(t, V3D_ORIENT_GLOBAL, constraint_axis, msg1);
      }
    }
    else if (!edit_2d) {
      if (cmode != axis) {
        /* First press, constraint to an axis. */
        t->orientation.index = 0;
        const short *orientation_ptr = t->orientation.types[t->orientation.index];
        const short orientation = orientation_ptr ? *orientation_ptr : V3D_ORIENT_GLOBAL;
        if (is_plane == false) {
          setUserConstraint(t, orientation, constraint_axis, msg2);
        }
        else {
          setUserConstraint(t, orientation, constraint_plane, msg3);
        }
      }
      else {
        /* Successive presses on existing axis, cycle orientation modes. */
        t->orientation.index = (t->orientation.index + 1) % ARRAY_SIZE(t->orientation.types);

        if (t->orientation.index == 0) {
          stopConstraint(t);
        }
        else {
          const short *orientation_ptr = t->orientation.types[t->orientation.index];
          const short orientation = orientation_ptr ? *orientation_ptr : V3D_ORIENT_GLOBAL;
          if (is_plane == false) {
            setUserConstraint(t, orientation, constraint_axis, msg2);
          }
          else {
            setUserConstraint(t, orientation, constraint_plane, msg3);
          }
        }
      }
    }
    t->redraw |= TREDRAW_HARD;
  }
}

int transformEvent(TransInfo *t, const wmEvent *event)
{
  char cmode = constraintModeToChar(t);
  bool handled = false;
  const int modifiers_prev = t->modifiers;
  const int mode_prev = t->mode;

  t->redraw |= handleMouseInput(t, &t->mouse, event);

  /* Handle modal numinput events first, if already activated. */
  if (((event->val == KM_PRESS) || (event->type == EVT_MODAL_MAP)) && hasNumInput(&t->num) &&
      handleNumInput(t->context, &(t->num), event)) {
    t->redraw |= TREDRAW_HARD;
    handled = true;
  }
  else if (event->type == MOUSEMOVE) {
    if (t->modifiers & MOD_CONSTRAINT_SELECT) {
      t->con.mode |= CON_SELECT;
    }

    copy_v2_v2_int(t->mval, event->mval);

    /* Use this for soft redraw. Might cause flicker in object mode */
    // t->redraw |= TREDRAW_SOFT;
    t->redraw |= TREDRAW_HARD;

    if (t->state == TRANS_STARTING) {
      t->state = TRANS_RUNNING;
    }

    applyMouseInput(t, &t->mouse, t->mval, t->values);

    // Snapping mouse move events
    t->redraw |= handleSnapping(t, event);
    handled = true;
  }
  /* handle modal keymap first */
  else if (event->type == EVT_MODAL_MAP) {
    switch (event->val) {
      case TFM_MODAL_CANCEL:
        t->state = TRANS_CANCEL;
        handled = true;
        break;
      case TFM_MODAL_CONFIRM:
        t->state = TRANS_CONFIRM;
        handled = true;
        break;
      case TFM_MODAL_TRANSLATE:
        /* only switch when... */
        if (ELEM(t->mode,
                 TFM_ROTATION,
                 TFM_RESIZE,
                 TFM_TRACKBALL,
                 TFM_EDGE_SLIDE,
                 TFM_VERT_SLIDE)) {
          restoreTransObjects(t);
          resetTransModal(t);
          resetTransRestrictions(t);
          transform_mode_init(t, NULL, TFM_TRANSLATION);
          initSnapping(t, NULL);  // need to reinit after mode change
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        else if (t->mode == TFM_SEQ_SLIDE) {
          t->flag ^= T_ALT_TRANSFORM;
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        else {
          if (t->obedit_type == OB_MESH) {
            if ((t->mode == TFM_TRANSLATION) && (t->spacetype == SPACE_VIEW3D)) {
              restoreTransObjects(t);
              resetTransModal(t);
              resetTransRestrictions(t);

              /* first try edge slide */
              transform_mode_init(t, NULL, TFM_EDGE_SLIDE);
              /* if that fails, do vertex slide */
              if (t->state == TRANS_CANCEL) {
                resetTransModal(t);
                t->state = TRANS_STARTING;
                transform_mode_init(t, NULL, TFM_VERT_SLIDE);
              }
              /* vert slide can fail on unconnected vertices (rare but possible) */
              if (t->state == TRANS_CANCEL) {
                resetTransModal(t);
                t->state = TRANS_STARTING;
                restoreTransObjects(t);
                resetTransRestrictions(t);
                transform_mode_init(t, NULL, TFM_TRANSLATION);
              }
              initSnapping(t, NULL);  // need to reinit after mode change
              t->redraw |= TREDRAW_HARD;
              handled = true;
            }
          }
          else if (t->options & (CTX_MOVIECLIP | CTX_MASK)) {
            if (t->mode == TFM_TRANSLATION) {
              restoreTransObjects(t);

              t->flag ^= T_ALT_TRANSFORM;
              t->redraw |= TREDRAW_HARD;
              handled = true;
            }
          }
        }
        break;
      case TFM_MODAL_ROTATE:
        /* only switch when... */
        if (!(t->options & CTX_TEXTURE) && !(t->options & (CTX_MOVIECLIP | CTX_MASK))) {
          if (ELEM(t->mode,
                   TFM_ROTATION,
                   TFM_RESIZE,
                   TFM_TRACKBALL,
                   TFM_TRANSLATION,
                   TFM_EDGE_SLIDE,
                   TFM_VERT_SLIDE)) {
            restoreTransObjects(t);
            resetTransModal(t);
            resetTransRestrictions(t);

            if (t->mode == TFM_ROTATION) {
              transform_mode_init(t, NULL, TFM_TRACKBALL);
            }
            else {
              transform_mode_init(t, NULL, TFM_ROTATION);
            }
            initSnapping(t, NULL);  // need to reinit after mode change
            t->redraw |= TREDRAW_HARD;
            handled = true;
          }
        }
        break;
      case TFM_MODAL_RESIZE:
        /* only switch when... */
        if (ELEM(t->mode,
                 TFM_ROTATION,
                 TFM_TRANSLATION,
                 TFM_TRACKBALL,
                 TFM_EDGE_SLIDE,
                 TFM_VERT_SLIDE)) {

          /* Scale isn't normally very useful after extrude along normals, see T39756 */
          if ((t->con.mode & CON_APPLY) && (t->con.orientation == V3D_ORIENT_NORMAL)) {
            stopConstraint(t);
          }

          restoreTransObjects(t);
          resetTransModal(t);
          resetTransRestrictions(t);
          transform_mode_init(t, NULL, TFM_RESIZE);
          initSnapping(t, NULL);  // need to reinit after mode change
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        else if (t->mode == TFM_SHRINKFATTEN) {
          t->flag ^= T_ALT_TRANSFORM;
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        else if (t->mode == TFM_RESIZE) {
          if (t->options & CTX_MOVIECLIP) {
            restoreTransObjects(t);

            t->flag ^= T_ALT_TRANSFORM;
            t->redraw |= TREDRAW_HARD;
            handled = true;
          }
        }
        break;

      case TFM_MODAL_SNAP_INV_ON:
        t->modifiers |= MOD_SNAP_INVERT;
        t->redraw |= TREDRAW_HARD;
        handled = true;
        break;
      case TFM_MODAL_SNAP_INV_OFF:
        t->modifiers &= ~MOD_SNAP_INVERT;
        t->redraw |= TREDRAW_HARD;
        handled = true;
        break;
      case TFM_MODAL_SNAP_TOGGLE:
        t->modifiers ^= MOD_SNAP;
        t->redraw |= TREDRAW_HARD;
        handled = true;
        break;
      case TFM_MODAL_AXIS_X:
        if (!(t->flag & T_NO_CONSTRAINT)) {
          transform_event_xyz_constraint(t, XKEY, cmode, false);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_AXIS_Y:
        if ((t->flag & T_NO_CONSTRAINT) == 0) {
          transform_event_xyz_constraint(t, YKEY, cmode, false);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_AXIS_Z:
        if ((t->flag & (T_NO_CONSTRAINT)) == 0) {
          transform_event_xyz_constraint(t, ZKEY, cmode, false);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_PLANE_X:
        if ((t->flag & (T_NO_CONSTRAINT | T_2D_EDIT)) == 0) {
          transform_event_xyz_constraint(t, XKEY, cmode, true);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_PLANE_Y:
        if ((t->flag & (T_NO_CONSTRAINT | T_2D_EDIT)) == 0) {
          transform_event_xyz_constraint(t, YKEY, cmode, true);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_PLANE_Z:
        if ((t->flag & (T_NO_CONSTRAINT | T_2D_EDIT)) == 0) {
          transform_event_xyz_constraint(t, ZKEY, cmode, true);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_CONS_OFF:
        if ((t->flag & T_NO_CONSTRAINT) == 0) {
          stopConstraint(t);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_ADD_SNAP:
        addSnapPoint(t);
        t->redraw |= TREDRAW_HARD;
        handled = true;
        break;
      case TFM_MODAL_REMOVE_SNAP:
        removeSnapPoint(t);
        t->redraw |= TREDRAW_HARD;
        handled = true;
        break;
      case TFM_MODAL_PROPSIZE:
        /* MOUSEPAN usage... */
        if (t->flag & T_PROP_EDIT) {
          float fac = 1.0f + 0.005f * (event->y - event->prevy);
          t->prop_size *= fac;
          if (t->spacetype == SPACE_VIEW3D && t->persp != RV3D_ORTHO) {
            t->prop_size = max_ff(min_ff(t->prop_size, ((View3D *)t->view)->clip_end),
                                  T_PROP_SIZE_MIN);
          }
          else {
            t->prop_size = max_ff(min_ff(t->prop_size, T_PROP_SIZE_MAX), T_PROP_SIZE_MIN);
          }
          calculatePropRatio(t);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_PROPSIZE_UP:
        if (t->flag & T_PROP_EDIT) {
          t->prop_size *= (t->modifiers & MOD_PRECISION) ? 1.01f : 1.1f;
          if (t->spacetype == SPACE_VIEW3D && t->persp != RV3D_ORTHO) {
            t->prop_size = min_ff(t->prop_size, ((View3D *)t->view)->clip_end);
          }
          else {
            t->prop_size = min_ff(t->prop_size, T_PROP_SIZE_MAX);
          }
          calculatePropRatio(t);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_PROPSIZE_DOWN:
        if (t->flag & T_PROP_EDIT) {
          t->prop_size /= (t->modifiers & MOD_PRECISION) ? 1.01f : 1.1f;
          t->prop_size = max_ff(t->prop_size, T_PROP_SIZE_MIN);
          calculatePropRatio(t);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_AUTOIK_LEN_INC:
        if (t->flag & T_AUTOIK) {
          transform_autoik_update(t, 1);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_AUTOIK_LEN_DEC:
        if (t->flag & T_AUTOIK) {
          transform_autoik_update(t, -1);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case TFM_MODAL_INSERTOFS_TOGGLE_DIR:
        if (t->spacetype == SPACE_NODE) {
          SpaceNode *snode = (SpaceNode *)t->sa->spacedata.first;

          BLI_assert(t->sa->spacetype == t->spacetype);

          if (snode->insert_ofs_dir == SNODE_INSERTOFS_DIR_RIGHT) {
            snode->insert_ofs_dir = SNODE_INSERTOFS_DIR_LEFT;
          }
          else if (snode->insert_ofs_dir == SNODE_INSERTOFS_DIR_LEFT) {
            snode->insert_ofs_dir = SNODE_INSERTOFS_DIR_RIGHT;
          }
          else {
            BLI_assert(0);
          }

          t->redraw |= TREDRAW_SOFT;
        }
        break;
      /* Those two are only handled in transform's own handler, see T44634! */
      case TFM_MODAL_EDGESLIDE_UP:
      case TFM_MODAL_EDGESLIDE_DOWN:
      default:
        break;
    }
  }
  /* else do non-mapped events */
  else if (event->val == KM_PRESS) {
    switch (event->type) {
      case RIGHTMOUSE:
        t->state = TRANS_CANCEL;
        handled = true;
        break;
      /* enforce redraw of transform when modifiers are used */
      case LEFTSHIFTKEY:
      case RIGHTSHIFTKEY:
        t->modifiers |= MOD_CONSTRAINT_PLANE;
        t->redraw |= TREDRAW_HARD;
        handled = true;
        break;

      case SPACEKEY:
        t->state = TRANS_CONFIRM;
        handled = true;
        break;

      case MIDDLEMOUSE:
        if ((t->flag & T_NO_CONSTRAINT) == 0) {
          /* exception for switching to dolly, or trackball, in camera view */
          if (t->flag & T_CAMERA) {
            if (t->mode == TFM_TRANSLATION) {
              setLocalConstraint(t, (CON_AXIS2), TIP_("along local Z"));
            }
            else if (t->mode == TFM_ROTATION) {
              restoreTransObjects(t);
              transform_mode_init(t, NULL, TFM_TRACKBALL);
            }
          }
          else {
            t->modifiers |= MOD_CONSTRAINT_SELECT;
            if (t->con.mode & CON_APPLY) {
              stopConstraint(t);
            }
            else {
              if (event->shift) {
                /* bit hackish... but it prevents mmb select to print the
                 * orientation from menu */
                float mati[3][3];
                strcpy(t->spacename, "global");
                unit_m3(mati);
                initSelectConstraint(t, mati);
              }
              else {
                initSelectConstraint(t, t->spacemtx);
              }
              postSelectConstraint(t);
            }
          }
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case ESCKEY:
        t->state = TRANS_CANCEL;
        handled = true;
        break;
      case PADENTER:
      case RETKEY:
        t->state = TRANS_CONFIRM;
        handled = true;
        break;
      case GKEY:
        /* only switch when... */
        if (ELEM(t->mode, TFM_ROTATION, TFM_RESIZE, TFM_TRACKBALL)) {
          restoreTransObjects(t);
          resetTransModal(t);
          resetTransRestrictions(t);
          transform_mode_init(t, NULL, TFM_TRANSLATION);
          initSnapping(t, NULL);  // need to reinit after mode change
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case SKEY:
        /* only switch when... */
        if (ELEM(t->mode, TFM_ROTATION, TFM_TRANSLATION, TFM_TRACKBALL)) {
          restoreTransObjects(t);
          resetTransModal(t);
          resetTransRestrictions(t);
          transform_mode_init(t, NULL, TFM_RESIZE);
          initSnapping(t, NULL);  // need to reinit after mode change
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case RKEY:
        /* only switch when... */
        if (!(t->options & CTX_TEXTURE)) {
          if (ELEM(t->mode, TFM_ROTATION, TFM_RESIZE, TFM_TRACKBALL, TFM_TRANSLATION)) {
            restoreTransObjects(t);
            resetTransModal(t);
            resetTransRestrictions(t);

            if (t->mode == TFM_ROTATION) {
              transform_mode_init(t, NULL, TFM_TRACKBALL);
            }
            else {
              transform_mode_init(t, NULL, TFM_ROTATION);
            }
            initSnapping(t, NULL);  // need to reinit after mode change
            t->redraw |= TREDRAW_HARD;
            handled = true;
          }
        }
        break;
      case CKEY:
        if (event->alt) {
          if (!(t->options & CTX_NO_PET)) {
            t->flag ^= T_PROP_CONNECTED;
            sort_trans_data_dist(t);
            calculatePropRatio(t);
            t->redraw = TREDRAW_HARD;
            handled = true;
          }
        }
        break;
      case OKEY:
        if (t->flag & T_PROP_EDIT && event->shift) {
          t->prop_mode = (t->prop_mode + 1) % PROP_MODE_MAX;
          calculatePropRatio(t);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case PADPLUSKEY:
        if (event->alt && t->flag & T_PROP_EDIT) {
          t->prop_size *= (t->modifiers & MOD_PRECISION) ? 1.01f : 1.1f;
          if (t->spacetype == SPACE_VIEW3D && t->persp != RV3D_ORTHO) {
            t->prop_size = min_ff(t->prop_size, ((View3D *)t->view)->clip_end);
          }
          calculatePropRatio(t);
          t->redraw = TREDRAW_HARD;
          handled = true;
        }
        break;
      case PAGEUPKEY:
      case WHEELDOWNMOUSE:
        if (t->flag & T_AUTOIK) {
          transform_autoik_update(t, 1);
        }
        else {
          view_editmove(event->type);
        }
        t->redraw = TREDRAW_HARD;
        handled = true;
        break;
      case PADMINUS:
        if (event->alt && t->flag & T_PROP_EDIT) {
          t->prop_size /= (t->modifiers & MOD_PRECISION) ? 1.01f : 1.1f;
          calculatePropRatio(t);
          t->redraw = TREDRAW_HARD;
          handled = true;
        }
        break;
      case PAGEDOWNKEY:
      case WHEELUPMOUSE:
        if (t->flag & T_AUTOIK) {
          transform_autoik_update(t, -1);
        }
        else {
          view_editmove(event->type);
        }
        t->redraw = TREDRAW_HARD;
        handled = true;
        break;
      case LEFTALTKEY:
      case RIGHTALTKEY:
        if (ELEM(t->spacetype, SPACE_SEQ, SPACE_VIEW3D)) {
          t->flag |= T_ALT_TRANSFORM;
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case NKEY:
        if (ELEM(t->mode, TFM_ROTATION)) {
          if ((t->flag & T_EDIT) && t->obedit_type == OB_MESH) {
            restoreTransObjects(t);
            resetTransModal(t);
            resetTransRestrictions(t);
            transform_mode_init(t, NULL, TFM_NORMAL_ROTATION);
            t->redraw = TREDRAW_HARD;
            handled = true;
          }
        }
        break;
      default:
        break;
    }

    /* Snapping key events */
    t->redraw |= handleSnapping(t, event);
  }
  else if (event->val == KM_RELEASE) {
    switch (event->type) {
      case LEFTSHIFTKEY:
      case RIGHTSHIFTKEY:
        t->modifiers &= ~MOD_CONSTRAINT_PLANE;
        t->redraw |= TREDRAW_HARD;
        handled = true;
        break;

      case MIDDLEMOUSE:
        if ((t->flag & T_NO_CONSTRAINT) == 0) {
          t->modifiers &= ~MOD_CONSTRAINT_SELECT;
          postSelectConstraint(t);
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      case LEFTALTKEY:
      case RIGHTALTKEY:
        if (ELEM(t->spacetype, SPACE_SEQ, SPACE_VIEW3D)) {
          t->flag &= ~T_ALT_TRANSFORM;
          t->redraw |= TREDRAW_HARD;
          handled = true;
        }
        break;
      default:
        break;
    }

    /* confirm transform if launch key is released after mouse move */
    if ((t->flag & T_RELEASE_CONFIRM) && event->type == t->launch_event) {
      t->state = TRANS_CONFIRM;
    }
  }

  /* if we change snap options, get the unsnapped values back */
  if ((mode_prev != t->mode) || ((t->modifiers & (MOD_SNAP | MOD_SNAP_INVERT)) !=
                                 (modifiers_prev & (MOD_SNAP | MOD_SNAP_INVERT)))) {
    applyMouseInput(t, &t->mouse, t->mval, t->values);
  }

  /* Per transform event, if present */
  if (t->handleEvent && (!handled ||
                         /* Needed for vertex slide, see [#38756] */
                         (event->type == MOUSEMOVE))) {
    t->redraw |= t->handleEvent(t, event);
  }

  /* Try to init modal numinput now, if possible. */
  if (!(handled || t->redraw) && ((event->val == KM_PRESS) || (event->type == EVT_MODAL_MAP)) &&
      handleNumInput(t->context, &(t->num), event)) {
    t->redraw |= TREDRAW_HARD;
    handled = true;
  }

  if (t->redraw && !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
    WM_window_status_area_tag_redraw(CTX_wm_window(t->context));
  }

  if (handled || t->redraw) {
    return 0;
  }
  else {
    return OPERATOR_PASS_THROUGH;
  }
}

bool calculateTransformCenter(bContext *C, int centerMode, float cent3d[3], float cent2d[2])
{
  TransInfo *t = MEM_callocN(sizeof(TransInfo), "TransInfo data");
  bool success;

  t->context = C;

  t->state = TRANS_RUNNING;

  /* avoid calculating PET */
  t->options = CTX_NO_PET;

  t->mode = TFM_DUMMY;

  initTransInfo(C, t, NULL, NULL);

  /* avoid doing connectivity lookups (when V3D_AROUND_LOCAL_ORIGINS is set) */
  t->around = V3D_AROUND_CENTER_BOUNDS;

  createTransData(C, t);  // make TransData structs from selection

  t->around = centerMode;  // override userdefined mode

  if (t->data_len_all == 0) {
    success = false;
  }
  else {
    success = true;

    calculateCenter(t);

    if (cent2d) {
      copy_v2_v2(cent2d, t->center2d);
    }

    if (cent3d) {
      // Copy center from constraint center. Transform center can be local
      copy_v3_v3(cent3d, t->center_global);
    }
  }

  /* aftertrans does insert keyframes, and clears base flags; doesn't read transdata */
  special_aftertrans_update(C, t);

  postTrans(C, t);

  MEM_freeN(t);

  return success;
}

static bool transinfo_show_overlay(const struct bContext *C, TransInfo *t, ARegion *ar)
{
  /* Don't show overlays when not the active view and when overlay is disabled: T57139 */
  bool ok = false;
  if (ar == t->ar) {
    ok = true;
  }
  else {
    ScrArea *sa = CTX_wm_area(C);
    if (sa->spacetype == SPACE_VIEW3D) {
      View3D *v3d = sa->spacedata.first;
      if ((v3d->flag2 & V3D_HIDE_OVERLAYS) == 0) {
        ok = true;
      }
    }
  }
  return ok;
}

static void drawTransformView(const struct bContext *C, ARegion *ar, void *arg)
{
  TransInfo *t = arg;

  if (!transinfo_show_overlay(C, t, ar)) {
    return;
  }

  GPU_line_width(1.0f);

  drawConstraint(t);
  drawPropCircle(C, t);
  drawSnapping(C, t);

  if (ar == t->ar) {
    /* edge slide, vert slide */
    drawEdgeSlide(t);
    drawVertSlide(t);

    /* Rotation */
    drawDial3d(t);
  }
}

/* just draw a little warning message in the top-right corner of the viewport
 * to warn that autokeying is enabled */
static void drawAutoKeyWarning(TransInfo *UNUSED(t), ARegion *ar)
{
  const char *printable = IFACE_("Auto Keying On");
  float printable_size[2];
  int xco, yco;

  const rcti *rect = ED_region_visible_rect(ar);

  const int font_id = BLF_default();
  BLF_width_and_height(
      font_id, printable, BLF_DRAW_STR_DUMMY_MAX, &printable_size[0], &printable_size[1]);

  xco = (rect->xmax - U.widget_unit) - (int)printable_size[0];
  yco = (rect->ymax - U.widget_unit);

  /* warning text (to clarify meaning of overlays)
   * - original color was red to match the icon, but that clashes badly with a less nasty border
   */
  unsigned char color[3];
  UI_GetThemeColorShade3ubv(TH_TEXT_HI, -50, color);
  BLF_color3ubv(font_id, color);
#ifdef WITH_INTERNATIONAL
  BLF_draw_default(xco, yco, 0.0f, printable, BLF_DRAW_STR_DUMMY_MAX);
#else
  BLF_draw_default_ascii(xco, yco, 0.0f, printable, BLF_DRAW_STR_DUMMY_MAX);
#endif

  /* autokey recording icon... */
  GPU_blend_set_func_separate(
      GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
  GPU_blend(true);

  xco -= U.widget_unit;
  yco -= (int)printable_size[1] / 2;

  UI_icon_draw(xco, yco, ICON_REC);

  GPU_blend(false);
}

static void drawTransformPixel(const struct bContext *C, ARegion *ar, void *arg)
{
  TransInfo *t = arg;

  if (!transinfo_show_overlay(C, t, ar)) {
    return;
  }

  if (ar == t->ar) {
    Scene *scene = t->scene;
    ViewLayer *view_layer = t->view_layer;
    Object *ob = OBACT(view_layer);

    /* draw auto-key-framing hint in the corner
     * - only draw if enabled (advanced users may be distracted/annoyed),
     *   for objects that will be autokeyframed (no point otherwise),
     *   AND only for the active region (as showing all is too overwhelming)
     */
    if ((U.autokey_flag & AUTOKEY_FLAG_NOWARNING) == 0) {
      if (ar == t->ar) {
        if (t->flag & (T_OBJECT | T_POSE)) {
          if (ob && autokeyframe_cfra_can_key(scene, &ob->id)) {
            drawAutoKeyWarning(t, ar);
          }
        }
      }
    }
  }
}

/**
 * \see #initTransform which reads values from the operator.
 */
void saveTransform(bContext *C, TransInfo *t, wmOperator *op)
{
  ToolSettings *ts = CTX_data_tool_settings(C);
  int proportional = 0;
  PropertyRNA *prop;

  // Save back mode in case we're in the generic operator
  if ((prop = RNA_struct_find_property(op->ptr, "mode"))) {
    RNA_property_enum_set(op->ptr, prop, t->mode);
  }

  if ((prop = RNA_struct_find_property(op->ptr, "value"))) {
    if (RNA_property_array_check(prop)) {
      RNA_property_float_set_array(op->ptr, prop, t->values_final);
    }
    else {
      RNA_property_float_set(op->ptr, prop, t->values_final[0]);
    }
  }

  if ((prop = RNA_struct_find_property(op->ptr, "center_override"))) {
    /* Important for redo operations. */
    RNA_property_float_set_array(op->ptr, prop, t->center_global);
  }

  if (t->flag & T_PROP_EDIT_ALL) {
    if (t->flag & T_PROP_EDIT) {
      proportional |= PROP_EDIT_USE;
    }
    if (t->flag & T_PROP_CONNECTED) {
      proportional |= PROP_EDIT_CONNECTED;
    }
    if (t->flag & T_PROP_PROJECTED) {
      proportional |= PROP_EDIT_PROJECTED;
    }
  }

  // If modal, save settings back in scene if not set as operator argument
  if ((t->flag & T_MODAL) || (op->flag & OP_IS_REPEAT)) {
    /* save settings if not set in operator */

    /* skip saving proportional edit if it was not actually used */
    if (!(t->options & CTX_NO_PET)) {
      if ((prop = RNA_struct_find_property(op->ptr, "use_proportional_edit")) &&
          !RNA_property_is_set(op->ptr, prop)) {
        if (t->spacetype == SPACE_GRAPH) {
          ts->proportional_fcurve = proportional;
        }
        else if (t->spacetype == SPACE_ACTION) {
          ts->proportional_action = proportional;
        }
        else if (t->obedit_type != -1) {
          ts->proportional_edit = proportional;
        }
        else if (t->options & CTX_MASK) {
          ts->proportional_mask = proportional != 0;
        }
        else if ((t->options & CTX_CURSOR) == 0) {
          ts->proportional_objects = proportional != 0;
        }
      }

      if ((prop = RNA_struct_find_property(op->ptr, "proportional_size"))) {
        ts->proportional_size = RNA_property_is_set(op->ptr, prop) ?
                                    RNA_property_float_get(op->ptr, prop) :
                                    t->prop_size;
      }

      if ((prop = RNA_struct_find_property(op->ptr, "proportional_edit_falloff")) &&
          !RNA_property_is_set(op->ptr, prop)) {
        ts->prop_mode = t->prop_mode;
      }
    }

    if (t->spacetype == SPACE_VIEW3D) {
      if ((prop = RNA_struct_find_property(op->ptr, "orient_type")) &&
          !RNA_property_is_set(op->ptr, prop) &&
          (t->orientation.user != V3D_ORIENT_CUSTOM_MATRIX)) {
        TransformOrientationSlot *orient_slot = &t->scene->orientation_slots[SCE_ORIENT_DEFAULT];
        orient_slot->type = t->orientation.user;
        BLI_assert(((orient_slot->index_custom == -1) && (t->orientation.custom == NULL)) ||
                   (BKE_scene_transform_orientation_get_index(t->scene, t->orientation.custom) ==
                    orient_slot->index_custom));
      }
    }
  }

  if (t->flag & T_MODAL) {
    /* do we check for parameter? */
    if (transformModeUseSnap(t)) {
      if (t->modifiers & MOD_SNAP) {
        ts->snap_flag |= SCE_SNAP;
      }
      else {
        ts->snap_flag &= ~SCE_SNAP;
      }
    }
  }

  if ((prop = RNA_struct_find_property(op->ptr, "use_proportional_edit"))) {
    RNA_property_boolean_set(op->ptr, prop, proportional & PROP_EDIT_USE);
    RNA_boolean_set(op->ptr, "use_proportional_connected", proportional & PROP_EDIT_CONNECTED);
    RNA_boolean_set(op->ptr, "use_proportional_projected", proportional & PROP_EDIT_PROJECTED);
    RNA_enum_set(op->ptr, "proportional_edit_falloff", t->prop_mode);
    RNA_float_set(op->ptr, "proportional_size", t->prop_size);
  }

  if ((prop = RNA_struct_find_property(op->ptr, "mirror"))) {
    RNA_property_boolean_set(op->ptr, prop, (t->flag & T_NO_MIRROR) == 0);
  }

  /* Orientation used for redo. */
  const bool use_orient_axis = (t->orient_matrix_is_set &&
                                (RNA_struct_find_property(op->ptr, "orient_axis") != NULL));
  short orientation;
  if (t->con.mode & CON_APPLY) {
    orientation = t->con.orientation;
    if (orientation == V3D_ORIENT_CUSTOM) {
      const int orientation_index_custom = BKE_scene_transform_orientation_get_index(
          t->scene, t->orientation.custom);
      /* Maybe we need a t->con.custom_orientation?
       * Seems like it would always match t->orientation.custom. */
      orientation = V3D_ORIENT_CUSTOM + orientation_index_custom;
      BLI_assert(orientation >= V3D_ORIENT_CUSTOM);
    }
  }
  else if ((t->orientation.user == V3D_ORIENT_CUSTOM_MATRIX) &&
           (prop = RNA_struct_find_property(op->ptr, "orient_matrix_type"))) {
    orientation = RNA_property_enum_get(op->ptr, prop);
  }
  else if (use_orient_axis) {
    /* We're not using an orientation, use the fallback. */
    orientation = t->orientation.unset;
  }
  else {
    orientation = V3D_ORIENT_GLOBAL;
  }

  if ((prop = RNA_struct_find_property(op->ptr, "orient_axis"))) {
    if (t->flag & T_MODAL) {
      if (t->con.mode & CON_APPLY) {
        int orient_axis = constraintModeToIndex(t);
        if (orient_axis != -1) {
          RNA_property_enum_set(op->ptr, prop, orient_axis);
        }
      }
      else {
        RNA_property_enum_set(op->ptr, prop, t->orient_axis);
      }
    }
  }
  if ((prop = RNA_struct_find_property(op->ptr, "orient_axis_ortho"))) {
    if (t->flag & T_MODAL) {
      RNA_property_enum_set(op->ptr, prop, t->orient_axis_ortho);
    }
  }

  if ((prop = RNA_struct_find_property(op->ptr, "orient_matrix"))) {
    if (t->flag & T_MODAL) {
      if (orientation != V3D_ORIENT_CUSTOM_MATRIX) {
        if (t->flag & T_MODAL) {
          RNA_enum_set(op->ptr, "orient_matrix_type", orientation);
        }
      }
      if (t->con.mode & CON_APPLY) {
        RNA_float_set_array(op->ptr, "orient_matrix", &t->con.mtx[0][0]);
      }
      else if (use_orient_axis) {
        RNA_float_set_array(op->ptr, "orient_matrix", &t->orient_matrix[0][0]);
      }
      else {
        RNA_float_set_array(op->ptr, "orient_matrix", &t->spacemtx[0][0]);
      }
    }
  }

  if ((prop = RNA_struct_find_property(op->ptr, "orient_type"))) {
    /* constraint orientation can be global, even if user selects something else
     * so use the orientation in the constraint if set */

    /* Use 'orient_matrix' instead. */
    if (t->flag & T_MODAL) {
      if (orientation != V3D_ORIENT_CUSTOM_MATRIX) {
        RNA_property_enum_set(op->ptr, prop, orientation);
      }
    }
  }

  if ((prop = RNA_struct_find_property(op->ptr, "constraint_axis"))) {
    bool constraint_axis[3] = {false, false, false};
    if (t->flag & T_MODAL) {
      /* Only set if needed, so we can hide in the UI when nothing is set.
       * See 'transform_poll_property'. */
      if (t->con.mode & CON_APPLY) {
        if (t->con.mode & CON_AXIS0) {
          constraint_axis[0] = true;
        }
        if (t->con.mode & CON_AXIS1) {
          constraint_axis[1] = true;
        }
        if (t->con.mode & CON_AXIS2) {
          constraint_axis[2] = true;
        }
      }
      if (ELEM(true, UNPACK3(constraint_axis))) {
        RNA_property_boolean_set_array(op->ptr, prop, constraint_axis);
      }
    }
  }

  {
    const char *prop_id = NULL;
    bool prop_state = true;
    if (t->mode == TFM_SHRINKFATTEN) {
      prop_id = "use_even_offset";
      prop_state = false;
    }

    if (prop_id && (prop = RNA_struct_find_property(op->ptr, prop_id))) {
      RNA_property_boolean_set(op->ptr, prop, ((t->flag & T_ALT_TRANSFORM) == 0) == prop_state);
    }
  }

  if ((t->options & CTX_SCULPT) && !(t->options & CTX_PAINT_CURVE)) {
    ED_sculpt_end_transform(C);
  }

  if ((prop = RNA_struct_find_property(op->ptr, "correct_uv"))) {
    RNA_property_boolean_set(
        op->ptr, prop, (t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT) != 0);
  }
}

static void initSnapSpatial(TransInfo *t, float r_snap[3])
{
  if (t->spacetype == SPACE_VIEW3D) {
    RegionView3D *rv3d = t->ar->regiondata;

    if (rv3d) {
      View3D *v3d = t->sa->spacedata.first;
      r_snap[0] = 0.0f;
      r_snap[1] = ED_view3d_grid_view_scale(t->scene, v3d, rv3d, NULL) * 1.0f;
      r_snap[2] = r_snap[1] * 0.1f;
    }
  }
  else if (t->spacetype == SPACE_IMAGE) {
    r_snap[0] = 0.0f;
    r_snap[1] = 0.0625f;
    r_snap[2] = 0.03125f;
  }
  else if (t->spacetype == SPACE_CLIP) {
    r_snap[0] = 0.0f;
    r_snap[1] = 0.125f;
    r_snap[2] = 0.0625f;
  }
  else if (t->spacetype == SPACE_NODE) {
    r_snap[0] = 0.0f;
    r_snap[1] = r_snap[2] = ED_node_grid_size();
  }
  else if (t->spacetype == SPACE_GRAPH) {
    r_snap[0] = 0.0f;
    r_snap[1] = 1.0;
    r_snap[2] = 0.1f;
  }
  else {
    r_snap[0] = 0.0f;
    r_snap[1] = r_snap[2] = 1.0f;
  }
}

/**
 * \note  caller needs to free 't' on a 0 return
 * \warning  \a event might be NULL (when tweaking from redo panel)
 * \see #saveTransform which writes these values back.
 */
bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *event, int mode)
{
  int options = 0;
  PropertyRNA *prop;

  t->context = C;

  /* added initialize, for external calls to set stuff in TransInfo, like undo string */

  t->state = TRANS_STARTING;

  if ((prop = RNA_struct_find_property(op->ptr, "cursor_transform")) &&
      RNA_property_is_set(op->ptr, prop)) {
    if (RNA_property_boolean_get(op->ptr, prop)) {
      options |= CTX_CURSOR;
    }
  }

  if ((prop = RNA_struct_find_property(op->ptr, "texture_space")) &&
      RNA_property_is_set(op->ptr, prop)) {
    if (RNA_property_boolean_get(op->ptr, prop)) {
      options |= CTX_TEXTURE;
    }
  }

  if ((prop = RNA_struct_find_property(op->ptr, "gpencil_strokes")) &&
      RNA_property_is_set(op->ptr, prop)) {
    if (RNA_property_boolean_get(op->ptr, prop)) {
      options |= CTX_GPENCIL_STROKES;
    }
  }

  if (CTX_wm_view3d(C) != NULL) {
    Object *ob = CTX_data_active_object(C);
    if (ob && ob->mode == OB_MODE_SCULPT && ob->sculpt) {
      options |= CTX_SCULPT;
    }
  }

  t->options = options;

  t->mode = mode;

  /* Needed to translate tweak events to mouse buttons. */
  t->launch_event = event ? WM_userdef_event_type_from_keymap_type(event->type) : -1;
  t->is_launch_event_tweak = event ? ISTWEAK(event->type) : false;

  /* XXX Remove this when wm_operator_call_internal doesn't use window->eventstate
   * (which can have type = 0) */
  /* For gizmo only, so assume LEFTMOUSE. */
  if (t->launch_event == 0) {
    t->launch_event = LEFTMOUSE;
  }

  unit_m3(t->spacemtx);

  initTransInfo(C, t, op, event);
  initTransformOrientation(C, t);

  if (t->spacetype == SPACE_VIEW3D) {
    t->draw_handle_apply = ED_region_draw_cb_activate(
        t->ar->type, drawTransformApply, t, REGION_DRAW_PRE_VIEW);
    t->draw_handle_view = ED_region_draw_cb_activate(
        t->ar->type, drawTransformView, t, REGION_DRAW_POST_VIEW);
    t->draw_handle_pixel = ED_region_draw_cb_activate(
        t->ar->type, drawTransformPixel, t, REGION_DRAW_POST_PIXEL);
    t->draw_handle_cursor = WM_paint_cursor_activate(CTX_wm_manager(C),
                                                     SPACE_TYPE_ANY,
                                                     RGN_TYPE_ANY,
                                                     transform_draw_cursor_poll,
                                                     transform_draw_cursor_draw,
                                                     t);
  }
  else if (t->spacetype == SPACE_IMAGE) {
    t->draw_handle_view = ED_region_draw_cb_activate(
        t->ar->type, drawTransformView, t, REGION_DRAW_POST_VIEW);
    t->draw_handle_cursor = WM_paint_cursor_activate(CTX_wm_manager(C),
                                                     SPACE_TYPE_ANY,
                                                     RGN_TYPE_ANY,
                                                     transform_draw_cursor_poll,
                                                     transform_draw_cursor_draw,
                                                     t);
  }
  else if (t->spacetype == SPACE_CLIP) {
    t->draw_handle_view = ED_region_draw_cb_activate(
        t->ar->type, drawTransformView, t, REGION_DRAW_POST_VIEW);
    t->draw_handle_cursor = WM_paint_cursor_activate(CTX_wm_manager(C),
                                                     SPACE_TYPE_ANY,
                                                     RGN_TYPE_ANY,
                                                     transform_draw_cursor_poll,
                                                     transform_draw_cursor_draw,
                                                     t);
  }
  else if (t->spacetype == SPACE_NODE) {
    t->draw_handle_view = ED_region_draw_cb_activate(
        t->ar->type, drawTransformView, t, REGION_DRAW_POST_VIEW);
    t->draw_handle_cursor = WM_paint_cursor_activate(CTX_wm_manager(C),
                                                     SPACE_TYPE_ANY,
                                                     RGN_TYPE_ANY,
                                                     transform_draw_cursor_poll,
                                                     transform_draw_cursor_draw,
                                                     t);
  }
  else if (t->spacetype == SPACE_GRAPH) {
    t->draw_handle_view = ED_region_draw_cb_activate(
        t->ar->type, drawTransformView, t, REGION_DRAW_POST_VIEW);
    t->draw_handle_cursor = WM_paint_cursor_activate(CTX_wm_manager(C),
                                                     SPACE_TYPE_ANY,
                                                     RGN_TYPE_ANY,
                                                     transform_draw_cursor_poll,
                                                     transform_draw_cursor_draw,
                                                     t);
  }
  else if (t->spacetype == SPACE_ACTION) {
    t->draw_handle_view = ED_region_draw_cb_activate(
        t->ar->type, drawTransformView, t, REGION_DRAW_POST_VIEW);
    t->draw_handle_cursor = WM_paint_cursor_activate(CTX_wm_manager(C),
                                                     SPACE_TYPE_ANY,
                                                     RGN_TYPE_ANY,
                                                     transform_draw_cursor_poll,
                                                     transform_draw_cursor_draw,
                                                     t);
  }

  createTransData(C, t);  // make TransData structs from selection

  if ((t->options & CTX_SCULPT) && !(t->options & CTX_PAINT_CURVE)) {
    ED_sculpt_init_transform(C);
  }

  if (t->data_len_all == 0) {
    postTrans(C, t);
    return 0;
  }

  if (event) {
    /* keymap for shortcut header prints */
    t->keymap = WM_keymap_active(CTX_wm_manager(C), op->type->modalkeymap);

    /* Stupid code to have Ctrl-Click on gizmo work ok.
     *
     * Do this only for translation/rotation/resize because only these
     * modes are available from gizmo and doing such check could
     * lead to keymap conflicts for other modes (see #31584)
     */
    if (ELEM(mode, TFM_TRANSLATION, TFM_ROTATION, TFM_RESIZE)) {
      wmKeyMapItem *kmi;

      for (kmi = t->keymap->items.first; kmi; kmi = kmi->next) {
        if (kmi->flag & KMI_INACTIVE) {
          continue;
        }

        if (kmi->propvalue == TFM_MODAL_SNAP_INV_ON && kmi->val == KM_PRESS) {
          if ((ELEM(kmi->type, LEFTCTRLKEY, RIGHTCTRLKEY) && event->ctrl) ||
              (ELEM(kmi->type, LEFTSHIFTKEY, RIGHTSHIFTKEY) && event->shift) ||
              (ELEM(kmi->type, LEFTALTKEY, RIGHTALTKEY) && event->alt) ||
              ((kmi->type == OSKEY) && event->oskey)) {
            t->modifiers |= MOD_SNAP_INVERT;
          }
          break;
        }
      }
    }
  }

  initSnapping(t, op);  // Initialize snapping data AFTER mode flags

  initSnapSpatial(t, t->snap_spatial);

  /* EVIL! posemode code can switch translation to rotate when 1 bone is selected.
   * will be removed (ton) */

  /* EVIL2: we gave as argument also texture space context bit... was cleared */

  /* EVIL3: extend mode for animation editors also switches modes...
   * but is best way to avoid duplicate code */
  mode = t->mode;

  calculatePropRatio(t);
  calculateCenter(t);

  /* Overwrite initial values if operator supplied a non-null vector.
   *
   * Run before init functions so 'values_modal_offset' can be applied on mouse input.
   */
  BLI_assert(is_zero_v4(t->values_modal_offset));
  if ((prop = RNA_struct_find_property(op->ptr, "value")) && RNA_property_is_set(op->ptr, prop)) {
    float values[4] = {0}; /* in case value isn't length 4, avoid uninitialized memory  */

    if (RNA_property_array_check(prop)) {
      RNA_float_get_array(op->ptr, "value", values);
    }
    else {
      values[0] = RNA_float_get(op->ptr, "value");
    }

    copy_v4_v4(t->values, values);

    if (t->flag & T_MODAL) {
      copy_v4_v4(t->values_modal_offset, values);
      t->redraw = TREDRAW_HARD;
    }
    else {
      copy_v4_v4(t->values, values);
      t->flag |= T_INPUT_IS_VALUES_FINAL;
    }
  }

  if (event) {
    /* Initialize accurate transform to settings requested by keymap. */
    bool use_accurate = false;
    if ((prop = RNA_struct_find_property(op->ptr, "use_accurate")) &&
        RNA_property_is_set(op->ptr, prop)) {
      if (RNA_property_boolean_get(op->ptr, prop)) {
        use_accurate = true;
      }
    }
    initMouseInput(t, &t->mouse, t->center2d, event->mval, use_accurate);
  }

  transform_mode_init(t, op, mode);

  if (t->state == TRANS_CANCEL) {
    postTrans(C, t);
    return 0;
  }

  /* Transformation axis from operator */
  if ((prop = RNA_struct_find_property(op->ptr, "orient_axis")) &&
      RNA_property_is_set(op->ptr, prop)) {
    t->orient_axis = RNA_property_enum_get(op->ptr, prop);
  }
  if ((prop = RNA_struct_find_property(op->ptr, "orient_axis_ortho")) &&
      RNA_property_is_set(op->ptr, prop)) {
    t->orient_axis_ortho = RNA_property_enum_get(op->ptr, prop);
  }

  /* Constraint init from operator */
  if ((t->flag & T_MODAL) ||
      /* For mirror operator the constraint axes are effectively the values. */
      (RNA_struct_find_property(op->ptr, "value") == NULL)) {
    if ((prop = RNA_struct_find_property(op->ptr, "constraint_axis")) &&
        RNA_property_is_set(op->ptr, prop)) {
      bool constraint_axis[3];

      RNA_property_boolean_get_array(op->ptr, prop, constraint_axis);

      if (constraint_axis[0] || constraint_axis[1] || constraint_axis[2]) {
        t->con.mode |= CON_APPLY;

        if (constraint_axis[0]) {
          t->con.mode |= CON_AXIS0;
        }
        if (constraint_axis[1]) {
          t->con.mode |= CON_AXIS1;
        }
        if (constraint_axis[2]) {
          t->con.mode |= CON_AXIS2;
        }

        setUserConstraint(t, t->orientation.user, t->con.mode, "%s");
      }
    }
  }
  else {
    /* So we can adjust in non global orientation. */
    if (t->orientation.user != V3D_ORIENT_GLOBAL) {
      t->con.mode |= CON_APPLY | CON_AXIS0 | CON_AXIS1 | CON_AXIS2;
      setUserConstraint(t, t->orientation.user, t->con.mode, "%s");
    }
  }

  /* Don't write into the values when non-modal because they are already set from operator redo
   * values. */
  if (t->flag & T_MODAL) {
    /* Setup the mouse input with initial values. */
    applyMouseInput(t, &t->mouse, t->mouse.imval, t->values);
  }

  if ((prop = RNA_struct_find_property(op->ptr, "preserve_clnor"))) {
    if ((t->flag & T_EDIT) && t->obedit_type == OB_MESH) {

      FOREACH_TRANS_DATA_CONTAINER (t, tc) {
        if ((((Mesh *)(tc->obedit->data))->flag & ME_AUTOSMOOTH)) {
          BMEditMesh *em = NULL;  // BKE_editmesh_from_object(t->obedit);
          bool do_skip = false;

          /* Currently only used for two of three most frequent transform ops,
           * can include more ops.
           * Note that scaling cannot be included here,
           * non-uniform scaling will affect normals. */
          if (ELEM(t->mode, TFM_TRANSLATION, TFM_ROTATION)) {
            if (em->bm->totvertsel == em->bm->totvert) {
              /* No need to invalidate if whole mesh is selected. */
              do_skip = true;
            }
          }

          if (t->flag & T_MODAL) {
            RNA_property_boolean_set(op->ptr, prop, false);
          }
          else if (!do_skip) {
            const bool preserve_clnor = RNA_property_boolean_get(op->ptr, prop);
            if (preserve_clnor) {
              BKE_editmesh_lnorspace_update(em, tc->obedit->data);
              t->flag |= T_CLNOR_REBUILD;
            }
            BM_lnorspace_invalidate(em->bm, true);
          }
        }
      }
    }
  }

  t->context = NULL;

  return 1;
}

void transformApply(bContext *C, TransInfo *t)
{
  t->context = C;

  if ((t->redraw & TREDRAW_HARD) || (t->draw_handle_apply == NULL && (t->redraw & TREDRAW_SOFT))) {
    selectConstraint(t);
    if (t->transform) {
      t->transform(t, t->mval);  // calls recalcData()
      viewRedrawForce(C, t);
    }
    t->redraw = TREDRAW_NOTHING;
  }
  else if (t->redraw & TREDRAW_SOFT) {
    viewRedrawForce(C, t);
  }

  /* If auto confirm is on, break after one pass */
  if (t->options & CTX_AUTOCONFIRM) {
    t->state = TRANS_CONFIRM;
  }

  t->context = NULL;
}

static void drawTransformApply(const bContext *C, ARegion *UNUSED(ar), void *arg)
{
  TransInfo *t = arg;

  if (t->redraw & TREDRAW_SOFT) {
    t->redraw |= TREDRAW_HARD;
    transformApply((bContext *)C, t);
  }
}

int transformEnd(bContext *C, TransInfo *t)
{
  int exit_code = OPERATOR_RUNNING_MODAL;

  t->context = C;

  if (t->state != TRANS_STARTING && t->state != TRANS_RUNNING) {
    /* handle restoring objects */
    if (t->state == TRANS_CANCEL) {
      /* exception, edge slide transformed UVs too */
      if (t->mode == TFM_EDGE_SLIDE) {
        doEdgeSlide(t, 0.0f);
      }
      else if (t->mode == TFM_VERT_SLIDE) {
        doVertSlide(t, 0.0f);
      }

      exit_code = OPERATOR_CANCELLED;
      restoreTransObjects(t);  // calls recalcData()
    }
    else {
      if (t->flag & T_CLNOR_REBUILD) {
        FOREACH_TRANS_DATA_CONTAINER (t, tc) {
          BMEditMesh *em = BKE_editmesh_from_object(tc->obedit);
          BM_lnorspace_rebuild(em->bm, true);
        }
      }
      exit_code = OPERATOR_FINISHED;
    }

    /* aftertrans does insert keyframes, and clears base flags; doesn't read transdata */
    special_aftertrans_update(C, t);

    /* Free data, also handles overlap [in freeTransCustomData()]. */
    postTrans(C, t);

    /* send events out for redraws */
    viewRedrawPost(C, t);

    viewRedrawForce(C, t);
  }

  t->context = NULL;

  return exit_code;
}

/* TODO, move to: transform_query.c */
bool checkUseAxisMatrix(TransInfo *t)
{
  /* currently only checks for editmode */
  if (t->flag & T_EDIT) {
    if ((t->around == V3D_AROUND_LOCAL_ORIGINS) &&
        (ELEM(t->obedit_type, OB_MESH, OB_CURVE, OB_MBALL, OB_ARMATURE))) {
      /* not all editmode supports axis-matrix */
      return true;
    }
  }

  return false;
}
